package com.atguigu.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import com.atguigu.client.ProductFeignClient;
import com.atguigu.dao.ProductRepository;
import com.atguigu.entity.*;
import com.atguigu.search.*;
import com.atguigu.service.SearchService;
import lombok.SneakyThrows;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    ProductRepository productRepository;
    @Autowired
    ProductFeignClient productFeignClient;
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    RestHighLevelClient restHighLevelClient;

    @Override
    public void onSale(Long skuId) {
        Product product = new Product();
        //商品的基本信息
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        if (skuInfo!=null){
            product.setId(skuInfo.getId());
            product.setProductName(skuInfo.getSkuName());
            product.setCreateTime(new Date());
            product.setPrice(skuInfo.getPrice().doubleValue());
            product.setDefaultImage(skuInfo.getSkuDefaultImg());
        //商品的品牌信息
            BaseBrand brand = productFeignClient.getBrandById(skuInfo.getBrandId());
            if (brand!=null){
                product.setBrandId(brand.getId());
                product.setBrandName(brand.getBrandName());
                product.setBrandLogoUrl(brand.getBrandLogoUrl());
            }
            //商品的分类信息
            BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
            if (categoryView!=null){
                product.setCategory1Id(categoryView.getCategory1Id());
                product.setCategory1Name(categoryView.getCategory1Name());
                product.setCategory2Id(categoryView.getCategory2Id());
                product.setCategory2Name(categoryView.getCategory2Name());
                product.setCategory3Id(categoryView.getCategory3Id());
                product.setCategory3Name(categoryView.getCategory3Name());
            }
            //平台的属性信息
            List<PlatformPropertyKey> platformPropertyList = productFeignClient.getPlatformPropertyBySkuId(skuId);
            if (!CollectionUtils.isEmpty(platformPropertyList)){
                List<SearchPlatformProperty> searchPlatformProperties = platformPropertyList.stream().map(PlatformPropertyKey -> {
                    SearchPlatformProperty platformProperty = new SearchPlatformProperty();
                    //设置平台属性id
                    platformProperty.setPropertyKeyId(PlatformPropertyKey.getId());
                    //平台属性名称
                    platformProperty.setPropertyKey(PlatformPropertyKey.getPropertyKey());
                    //平台属性值名称  获取平台属性值集合对象
                    PlatformPropertyValue platformPropertyValue = PlatformPropertyKey.getPropertyValueList().get(0);
                    platformProperty.setPropertyValue(platformPropertyValue.getPropertyValue());

                    return platformProperty;
                }).collect(Collectors.toList());
                product.setPlatformProperty(searchPlatformProperties);
            }

        }
        //上述信息方法到es中
        productRepository.save(product);
    }

    //商品的下架
    @Override
    public void offSale(Long skuId) {

        productRepository.deleteById(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        //定义一个缓存的key
        String hotKey = "hotScore";
        //为什么要用 zset有序集合 需要实现有序自增
        double count = redisTemplate.opsForZSet().incrementScore(hotKey, "skuId:" + skuId, 1);
        //判断什么时候更新es
        if (count % 10 == 0) {
            //更新es将原有数据中的 "hotScore" : 0 改为最新的
            Optional<Product> optional = productRepository.findById(skuId);
            Product product = optional.get();
            product.setHotScore(Math.round(count));
            //保存到es
            productRepository.save(product);

        }
    }

    //商品的搜索功能
    @SneakyThrows
    @Override
    public SearchResponseVo searchProduct(SearchParam searchParam) {
        //1.生成动态查询dsl语句
        SearchRequest searchRequest = this.buildQueryDsl(searchParam);
        //2.通过语句实现语句查询
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //3.将查询结果封装到对象当中
        SearchResponseVo searchResponseVo = parseSearchResult(searchResponse);
        //4.其他返回参数的赋值
        searchResponseVo.setPageNo(searchParam.getPageNo());
        searchResponseVo.setPageSize(searchParam.getPageSize());
        //5.设置总页数
        boolean flag = searchResponseVo.getTotal()%searchParam.getPageSize()==0;
        long totalPages = 0;
        if(flag){
            totalPages = searchResponseVo.getTotal()/searchParam.getPageSize();
        }else{
            totalPages = searchResponseVo.getTotal()/searchParam.getPageSize()+1;
        }
        searchResponseVo.setTotalPages(totalPages);
        return searchResponseVo;

    }

    //将查询结果封装到对象当中
    private SearchResponseVo parseSearchResult(SearchResponse searchResponse) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        //1.拿到商品的基本信息
        SearchHits firstHits = searchResponse.getHits();
        SearchHit[] secondHits = firstHits.getHits();
        List<Product> productList = new ArrayList<>();
        if (null != secondHits && secondHits.length > 0) {
            for (SearchHit secondHit : secondHits) {
                //把得到的商品信息转换为product对象
                Product product = JSONObject.parseObject(secondHit.getSourceAsString(), Product.class);
                //看信息里面是否有高亮信息
                if (secondHit.getHighlightFields().get("productName") != null) {
                    //拿取高亮信息
                    Text productName = secondHit.getHighlightFields().get("productName").getFragments()[0];
                    product.setProductName(productName.toString());
                }
                //一个商品信息
                productList.add(product);
            }
        }
        //把多个商品信息放入searchResponseVo返回给前端
        searchResponseVo.setProductList(productList);
        searchResponseVo.setTotal(firstHits.totalHits);

        //2.获取品牌brandIdAgg聚合数据信息
        ParsedLongTerms brandIdAgg = searchResponse.getAggregations().get("brandIdAgg");
        //迭代里面的每个bucket
        List<SearchBrandVo> brandVoList = brandIdAgg.getBuckets().stream().map(bucket -> {
            SearchBrandVo searchBrandVo = new SearchBrandVo();
            //获取品牌id的值
            String brandId = bucket.getKeyAsString();
            searchBrandVo.setBrandId(Long.parseLong(brandId));

            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brandNameAgg");
            //获取品牌的名称 由于在这里只有一条记录所有取第一条
            String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
            searchBrandVo.setBrandName(brandName);

            ParsedStringTerms brandLogoUrlAgg = bucket.getAggregations().get("brandLogoUrlAgg");
            //获取品牌的logoUrl
            String brandLogoUrl = brandLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchBrandVo.setBrandLogoUrl(brandLogoUrl);

            return searchBrandVo;
        }).collect(Collectors.toList());
        //把品牌集合信息放到一个集合中
        searchResponseVo.setBrandVoList(brandVoList);

        //3.获取嵌套平台销售属性-platformPropertyAgg
        ParsedNested platformPropertyAgg = searchResponse.getAggregations().get("platformPropertyAgg");
        //获取嵌套平台销售属性-propertyKeyIdAgg
        ParsedLongTerms propertyKeyIdAgg = platformPropertyAgg.getAggregations().get("propertyKeyIdAgg");
        List<? extends Terms.Bucket> buckets = propertyKeyIdAgg.getBuckets();
        if (!CollectionUtils.isEmpty(buckets)) {
            //对propertyKeyIdAgg聚合的bucket进行迭代
            List<SearchPlatformPropertyVo> platformPropertyList = buckets.stream().map(bucket -> {
                SearchPlatformPropertyVo searchPlatformPropertyVo = new SearchPlatformPropertyVo();
                //获取平台属性id的值
                Number propertyKeyId = bucket.getKeyAsNumber();
                searchPlatformPropertyVo.setPropertyKeyId(propertyKeyId.longValue());

                //获取平台属性key(propertyKeyAgg)的聚合信息:平台属性名称
                ParsedStringTerms propertyKeyAgg = bucket.getAggregations().get("propertyKeyAgg");
                //获取平台属性名称
                String propertyKey = propertyKeyAgg.getBuckets().get(0).getKeyAsString();
                searchPlatformPropertyVo.setPropertyKey(propertyKey);

                //获取平台属性值(propertyValueAgg)的聚合信息
                ParsedStringTerms propertyValueAgg = bucket.getAggregations().get("propertyValueAgg");
                List<? extends Terms.Bucket> propertyValueBucketList = propertyValueAgg.getBuckets();
                //获取平台属性值集合信息
                List<String> propertyValueList = propertyValueBucketList.stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
                searchPlatformPropertyVo.setPropertyValueList(propertyValueList);

                return searchPlatformPropertyVo;
            }).collect(Collectors.toList());
            searchResponseVo.setPlatformPropertyList(platformPropertyList);
        }
        return searchResponseVo;

    }

    //生成动态查询dsl语句
    private SearchRequest buildQueryDsl(SearchParam searchParam) {
        //1.构建bool字段
        BoolQueryBuilder firstBool = QueryBuilders.boolQuery();
        //2.构造分类id过滤器
        if (!StringUtils.isEmpty(searchParam.getCategory1Id())){
            //构造一级分类id过滤器
            TermQueryBuilder category1Id = QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id());
            firstBool.filter(category1Id);
        }
        if (!StringUtils.isEmpty(searchParam.getCategory2Id())){
            //构造二级分类id过滤器
            TermQueryBuilder category2Id = QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id());
            firstBool.filter(category2Id);
        }
        if (!StringUtils.isEmpty(searchParam.getCategory3Id())) {
            //三级分类Id 把term放到filter中
            TermQueryBuilder category3Id = QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id());
            firstBool.filter(category3Id);
        }

        //3.构造品牌id过滤条件 参数格式brandName=2:三星
        String brandName = searchParam.getBrandName();
        if (!StringUtils.isEmpty(brandName)){
            String[] brandParam = brandName.split(":");//用来分割
            if (null != brandParam && brandParam.length == 2){
                firstBool.filter(QueryBuilders.termQuery("brandId", brandParam[0]));
            }
        }

        //4.构造平台属性过滤条件
        //页面传递数据格式http://search.gmall.com/search.html?keyword=三星&props=23:4G:运行内存&props=24:128G:机身内存
        String[] props = searchParam.getProps();
        if (null != props && props.length > 0){
            for (String prop : props) {
                //属性id,属性值,属性名称 prop=23:4G:运行内存,prop=24:128G:机身内存
                String[] platformParams  = prop.split(":");//用来分割
                if(null != platformParams && platformParams.length == 3){
                    //a.构建内部bool
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                    //c.构建子查询条件bool
                    BoolQueryBuilder childBoolQuery = QueryBuilders.boolQuery();
                    //d.构造属性id,属性值的must条件
                    childBoolQuery.must(QueryBuilders.termQuery("platformProperty.propertyKeyId", platformParams[0]));
                    childBoolQuery.must(QueryBuilders.termQuery("platformProperty.propertyValue", platformParams[1]));
                    //b.构造must下面的嵌套
                    boolQuery.must(QueryBuilders.nestedQuery("platformProperty", childBoolQuery, ScoreMode.None));
                    //e.添加到第一个bool中
                    firstBool.filter(boolQuery);
                }
            }
        }

        //5.构造商品名称查询条件 http://search.gmall.com/search.html?keyword=三星
        String keyword = searchParam.getKeyword();
        if (!StringUtils.isEmpty(keyword)) {
            //Operator.AND表示 三星手机在productName中分词[三星,手机]必须同时存在才会查到
            //Operator.OR 有其中一个即可！
            MatchQueryBuilder productName = QueryBuilders.matchQuery("productName", keyword).operator(Operator.AND);
            firstBool.must(productName);
        }

        //构建dsl查询器{}
        //6.把query查询条件设置进去
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(firstBool);

        //7.设置分页 起始页公式：(pageNo-1)*pageSize
        int from = (searchParam.getPageNo() - 1) * searchParam.getPageSize();
        sourceBuilder.from(from);
        sourceBuilder.size(searchParam.getPageSize());

        /**
         * 8.设置排序
         * 格式http://search.gmall.com/search.html?keyword=三星&props=23:4G:运行内存&order=2:desc
         * 综合=1 价格=2 ---> hotScore=1 price=2
         * &order=1:desc表示按照热度降序排列
         * 如果格式不正确：http://search.gmall.com/search.html?keyword=三星&props=23:4G:运行内存&order=2
         * 给默认排序规则
         */
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)) {
            String[] orderParams = order.split(":");//用来分割
            if (null != orderParams && orderParams.length == 2) {
                String field = null;
                switch (orderParams[0]) {
                    case "1":
                        field = "hotScore";
                        break;
                    case "2":
                        field = "price";
                        break;
                }
                //升序还是降序
                sourceBuilder.sort(field, "asc".equals(orderParams[1]) ? SortOrder.ASC : SortOrder.DESC);
            } else {
                //按照默认排序
                sourceBuilder.sort("hotScore", SortOrder.DESC);
            }
        }

        //9.设置高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("productName");
        highlightBuilder.preTags("<span style=color:red>");
        highlightBuilder.postTags("</span>");
        sourceBuilder.highlighter(highlightBuilder);

        //10.设置品牌聚合
        TermsAggregationBuilder brandIdAggBuilder =
                AggregationBuilders.terms("brandIdAgg").field("brandId")
                        .subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName"))
                        .subAggregation(AggregationBuilders.terms("brandLogoUrlAgg").field("brandLogoUrl"));
        sourceBuilder.aggregation(brandIdAggBuilder);

        //11.设置平台属性值聚合
        NestedAggregationBuilder nestedAggregationBuilder = AggregationBuilders.nested("platformPropertyAgg", "platformProperty")
                .subAggregation(AggregationBuilders.terms("propertyKeyIdAgg").field("platformProperty.propertyKeyId")
                        .subAggregation(AggregationBuilders.terms("propertyKeyAgg").field("platformProperty.propertyKey"))
                        .subAggregation(AggregationBuilders.terms("propertyValueAgg").field("platformProperty.propertyValue")));
        sourceBuilder.aggregation(nestedAggregationBuilder);

        //12.设置要返回的字段
        sourceBuilder.fetchSource(new String[]{"id", "defaultImage", "productName", "price","hostScore"}, null);

        //13.要查询哪个index和type:哪个数据库和表
        SearchRequest searchRequest = new SearchRequest("product");
        searchRequest.types("info");

        //14.把整个查询拼接好的查询语句放到搜索请求当中
        searchRequest.source(sourceBuilder);
        System.out.println("拼接好的dsl:" + sourceBuilder.toString());
        return searchRequest;
    }
}
